!theme mono

skinparam sequenceArrowThickness 2
skinparam sequenceParticipantBorderThickness 2
skinparam sequenceActorBorderThickness 2
skinparam sequenceGroupBorderThickness 2

title Health Service 내부 시퀀스 다이어그램 (역설계 - 정상치 기준 비교 포함)

participant "HealthController" as HealthCtrl
participant "CheckupSyncUseCase" as SyncUC
participant "CheckupQueryUseCase" as QueryUC
participant "FileUploadUseCase" as FileUC
participant "HealthProfileDomainService" as HealthDomainSvc
participant "CheckupAnalysisDomainService" as AnalysisDomainSvc
participant "NormalRangeDomainService" as NormalDomainSvc
participant "HealthRepository" as HealthRepo
participant "HealthCheckupRawRepository" as RawRepo
participant "NormalRangeRepository" as NormalRepo
participant "UserServiceAdapter" as UserAdapter
participant "BlobStorageAdapter" as BlobAdapter
participant "CacheAdapter" as CacheAdapter
participant "EventPublisherAdapter" as EventAdapter
participant "PostgreSQL" as PostgreSQL
participant "Redis Cache" as Redis
participant "Azure Blob Storage" as BlobStorage
participant "Azure Service Bus" as ServiceBus

== 1. POST /api/health/checkup/sync (건강검진 결과 연동) ==

HealthCtrl -> SyncUC: syncCheckupData(userId)

SyncUC -> UserAdapter: getUserInfo(userId)
UserAdapter -> SyncUC: UserInfo 응답 (memberSerialNumber, gender 포함)

SyncUC -> RawRepo: findNhisCheckupDataByMemberSerial(memberSerialNumber)
RawRepo -> PostgreSQL: SELECT * FROM health_checkup_raw WHERE member_serial_number = ? ORDER BY reference_year DESC
PostgreSQL -> RawRepo: 건강보험공단 건강검진 원본 데이터

SyncUC -> HealthRepo: findByMemberSerialNumber(memberSerialNumber)
PostgreSQL -> HealthRepo: 기존 가공 데이터 조회

alt 기존 데이터가 없거나 더 최신 원본 데이터가 있는 경우
    SyncUC -> NormalRepo: getNormalRangesByGender(userGender)
    NormalRepo -> PostgreSQL: SELECT * FROM health_normal_ranges WHERE gender_code IN (0, ?) ORDER BY item_code
    note right: 성별별 + 공통 정상치 기준 조회
    PostgreSQL -> NormalRepo: 성별별/공통 정상치 기준 데이터

    SyncUC -> AnalysisDomainSvc: transformAndAnalyzeCheckupData(rawData, normalRanges, userGender)
    
    AnalysisDomainSvc -> AnalysisDomainSvc: validateAndConvertData(rawData)
    note right: 건강보험공단 데이터 검증 및 단위 변환
    
    AnalysisDomainSvc -> NormalDomainSvc: compareWithNormalRanges(checkupData, normalRanges)
    NormalDomainSvc -> NormalDomainSvc: evaluateEachIndicator(indicators, ranges)
    note right: **각 지표별 정상/주의/위험 판정**\n- BMI, 혈압, 혈당, 콜레스테롤 등\n- 성별별 기준 적용
    
    NormalDomainSvc -> NormalDomainSvc: calculateOverallRiskLevel(indicatorResults)
    note right: **종합 위험도 레벨 계산**\n- 정상: 80-100점\n- 주의: 60-79점\n- 위험: 0-59점
    
    NormalDomainSvc -> NormalDomainSvc: identifyAbnormalIndicators(indicatorResults)
    note right: 이상 항목 식별 및 JSON 배열 생성
    
    NormalDomainSvc -> AnalysisDomainSvc: NormalRangeAnalysisResult 반환
    
    AnalysisDomainSvc -> AnalysisDomainSvc: createHealthCheckupEntity(transformedData, analysisResult)
    AnalysisDomainSvc -> SyncUC: 변환된 HealthCheckupEntity (정상치 비교 결과 포함)

    SyncUC -> HealthRepo: saveOrUpdateHealthCheckup(healthCheckupEntity)
    HealthRepo -> PostgreSQL: INSERT/UPDATE health_checkups
    note right: 정상치 비교 결과도 함께 저장\n- abnormal_indicators: JSON 배열\n- health_score: 0-100점\n- risk_level: normal/caution/danger
    PostgreSQL -> HealthRepo: 저장 완료
end

SyncUC -> CacheAdapter: invalidateUserHealthCache(userId)
CacheAdapter -> Redis: DEL health:history:{userId} health:normal:{userId}

SyncUC -> EventAdapter: publishHealthDataSyncedEvent(userId, syncResult)
EventAdapter -> ServiceBus: 건강데이터 동기화 이벤트 발행

SyncUC -> HealthCtrl: HealthSyncResponse 반환
note right: {syncedRecords, newRecords, updatedRecords, skippedRecords, lastSyncedCheckup, message}

== 2. GET /api/health/checkup/history (건강검진 이력 조회) ==

HealthCtrl -> QueryUC: getHealthCheckupHistory(userId, limit)

QueryUC -> CacheAdapter: getCachedHealthHistory(userId)
CacheAdapter -> Redis: GET health:history:{userId}
Redis -> CacheAdapter: 캐시된 데이터 또는 null

alt 캐시 미스인 경우
    QueryUC -> HealthRepo: findCheckupHistoryWithDetails(userId, limit)
    HealthRepo -> PostgreSQL: SELECT * FROM health_checkups WHERE user_id = ? ORDER BY reference_year DESC LIMIT ?
    PostgreSQL -> HealthRepo: 건강검진 이력 데이터 (정상치 비교 결과 포함)
    
    QueryUC -> AnalysisDomainSvc: calculateTrendAnalysis(checkupHistory)
    AnalysisDomainSvc -> AnalysisDomainSvc: analyzeTrendsByIndicator(historyData)
    note right: **트렌드 분석**\n- 연도별 변화 추이\n- 개선/악화 항목 식별\n- 평균 건강점수 계산
    
    QueryUC -> CacheAdapter: cacheHealthHistory(userId, analysisResult)
    CacheAdapter -> Redis: SETEX health:history:{userId} 3600 {data}
    note right: 1시간 TTL로 캐싱
end

QueryUC -> HealthCtrl: HealthHistoryResponse 반환
note right: {checkupHistory, totalRecords, averageHealthScore, trendAnalysis, normalRangeReference}

== 3. POST /api/health/checkup/upload (건강검진 파일 업로드) ==

HealthCtrl -> FileUC: uploadCheckupFile(uploadRequest)
note right: {userId, fileName, fileType, fileContent}

FileUC -> FileUC: validateFileFormat(fileType, fileContent)
note right: 파일 형식 및 크기 검증 (최대 10MB)

FileUC -> BlobAdapter: uploadToAzureBlob(fileName, fileContent)
BlobAdapter -> BlobStorage: Azure Blob Storage Upload
BlobStorage -> BlobAdapter: 업로드 완료 응답

FileUC -> HealthRepo: saveFileMetadata(fileMetadata)
HealthRepo -> PostgreSQL: INSERT INTO health_files (user_id, file_name, file_url, file_type, upload_status)
PostgreSQL -> HealthRepo: 파일 메타데이터 저장 완료

FileUC -> HealthCtrl: FileUploadResponse 반환
note right: {fileId, uploadUrl, status, message}

== 4. GET /api/health/normal-ranges (정상치 기준 조회) ==

HealthCtrl -> QueryUC: getNormalRangesByGender(genderCode)

QueryUC -> CacheAdapter: getCachedNormalRanges(genderCode)
CacheAdapter -> Redis: GET normal:ranges:{genderCode}
Redis -> CacheAdapter: 캐시된 정상치 기준 또는 null

alt 캐시 미스인 경우
    QueryUC -> NormalRepo: getNormalRangesByGender(genderCode)
    NormalRepo -> PostgreSQL: SELECT * FROM health_normal_ranges WHERE gender_code IN (0, ?) ORDER BY item_code
    PostgreSQL -> NormalRepo: 성별별/공통 정상치 기준 데이터
    
    QueryUC -> CacheAdapter: cacheNormalRanges(genderCode, normalRanges)
    CacheAdapter -> Redis: SETEX normal:ranges:{genderCode} 86400 {data}
    note right: 24시간 TTL로 캐싱 (변경 빈도 낮음)
end

QueryUC -> HealthCtrl: NormalRangeResponse 반환
note right: {normalRanges: [{itemCode, itemName, genderCode, normalMin, normalMax, cautionMin, cautionMax, dangerMin, dangerMax, unit}]}

== 예외 처리 (정상치 관련 추가) ==

alt 정상치 기준 데이터 없음
    NormalRepo -> AnalysisDomainSvc: NoNormalRangeFoundException
    AnalysisDomainSvc -> SyncUC: 기본 정상치 기준 사용
    note right: 하드코딩된 기본값으로 대체
end

alt 정상치 비교 실패
    NormalDomainSvc -> AnalysisDomainSvc: NormalRangeComparisonException
    AnalysisDomainSvc -> SyncUC: 정상치 비교 없이 기본 저장
    note right: 기본 건강검진 데이터만 저장
end

alt 파일 업로드 실패
    BlobAdapter -> FileUC: BlobStorageException
    FileUC -> HealthRepo: updateUploadStatus(fileId, "FAILED")
    FileUC -> HealthCtrl: 500 Internal Server Error
end

== 캐싱 전략 (정상치 관련 추가) ==

note over QueryUC, CacheAdapter
**확장된 캐싱 전략**
- 건강검진 이력 + 정상치 비교: 1시간 캐시
- 정상치 기준 데이터: 24시간 캐시 (변경 빈도 낮음)
- 건강 점수 계산 결과: 포함 (이력과 함께)
- 트렌드 분석 결과: 포함 (이력과 함께)
- 파일 메타데이터: 30분 캐시
- 정상치 기준 업데이트 시 관련 캐시 무효화
end note